"""
------------------------------------------------------------------------------
Copyright (C) 2007 Zeb.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
------------------------------------------------------------------------------
"""

import os, sys

from lex import lex

from define import *

try:
    exec(file(sys.argv[3]).read())
    
except:
    import traceback
    import StringIO
    import sys
    
    excinfo = sys.exc_info()
    sio = StringIO.StringIO()
    traceback.print_exception(excinfo[0], excinfo[1], excinfo[2], None, sio)
    s = sio.getvalue()
    print traceback.format_exc()
    sio.close()  
        
class CppParser:
    def __init(self): 
        self._pyfileh = None
        self._pyfilecpp = None
        self._funcList = []
        
    def parse(self, config, outputDir, mod_inline, mod_inc_list, mod_api_list, mod_con_list):    
        self._config = config
                
        hfile = os.path.join(outputDir, config['py output file'] + '.h')
        self._pyfileh = open(hfile, 'w')
        self._pyfilecpp = open(os.path.join(outputDir, config['py output file'] + '.cpp'), 'w')
        
        register = self.__parseConfig()        
        self.__generateImpl()
        
        self._pyfileh.write('/* this is automatic generated by cpp2py, do not modifiy it manually. */\n')
        self._pyfileh.write(INLINE_TXT_H)

        if self._config['namespace']:
            self._pyfileh.write('namespace %s\n{\n' % self._config['namespace'])
            
        self._pyfilecpp.write('/* this is automatic generated by cpp2py, do not modifiy it manually. */\n')
        self._pyfilecpp.write(INLINE_TXT_CPP)
        mod_inline.add(INLINE_TXT_CPP)
        self._pyfilecpp.write('#include "%s.h"\n\n' % self._config['py output file'])
        mod_inc_list.append(self._config['py output file'])
        if self._config['namespace']:
            self._pyfilecpp.write('using namespace %s;\n\n' % self._config['namespace'])
            
        if self._config['namespace']:
            self._pyfileh.write('\t')
            
        self._pyfileh.write('extern PyMethodDef %s_API[%d];\n' % 
                (self._config['module name'], 1 + len(self._funcList)))
        mod_api_list.append([self._config['module name'], '%s::%s_API' % (self._config['namespace'], self._config['module name'])])

        constantList = self._config.get('constant list', None)
        if constantList:
            constantList = constantList.split(',')
            constantList = [i for i in constantList if i]
            if constantList:
                if self._config['namespace']:
                    self._pyfileh.write('\t')
                self._pyfileh.write('extern void %sInitConstant();\n' % self._config['method prefix'])
                self.__writeConstantList(constantList)
                mod_con_list.append('%s::%sInitConstant();' % (self._config['namespace'], self._config['method prefix']))
                
        if self._config['namespace']:
            self._pyfileh.write('}\n')        
        
        self.__writeResult()
        self._pyfilecpp.write(register)
        
        self._pyfileh.close()
        self._pyfileh = None
        
        self._pyfilecpp.close()
        self._pyfilecpp = None
                
    def __writeConstantList(self, constantList):
        txt = ''
        if self._config['namespace']:
            txt += 'namespace %s\n{\n\t' % self._config['namespace']

        txt += 'void %sInitConstant()\n\t{\n' % self._config['method prefix']
        
        if self._config['namespace']:
            txt += '\t\tPyObject* m = PyImport_AddModule("%s");\n' % self._config['module name']
        else:
            txt += '\tPyObject* m = PyImport_AddModule("%s");\n' % self._config['module name']
        
        if self._config['namespace']:
            for i in constantList:
                txt += '\t\tPyModule_AddIntConstant(m, "%s", %s::%s);\n' % (i, self._config['namespace'], i)
        else:
            for i in constantList:
                txt += '\tPyModule_AddIntConstant(m, %s, %s);\n' % (i, i)
                
        if self._config['namespace']:
            txt += '\t'
            
        txt += '}\n\n'
        
        if self._config['namespace']:
            txt += '}\n\n'

        self._pyfilecpp.write(txt)
        
    def __getFormatAtom(self, typeName):
        if typeName in INT_TYPE:
            return 'i'
        elif typeName in BYTE_TYPE:
            return 'B'
        elif typeName in ULONG_TYPE:
            return 'k'
        elif typeName in UINT_TYPE:
            return 'I'
        elif typeName in FLOAT_TYPE:
            return 'f'
        elif typeName in STR_TYPE or typeName in STD_STR_TYPE:
            if WCHAR:
                return 'u'
            else:
                return 's'
            
        return None  
   
    def __genStructInputFormat(self, struct):
        fmt = ''
        for i in struct._paramList:
            fmt += self.__genInputFormat(i._type, USAGE_IN)
        return fmt
    
    def __genInputFormat(self, typeDef, usage):
        if usage == USAGE_IN:
            if typeDef._type == TYPE_NORMAL:
                format = self.__getFormatAtom(typeDef._name)
                if format:
                    return format
                
                struct = STRUCT_DICT.get(typeDef._name, None)
                if struct == None:
                    raise 'ERROR: unknown type: %s' % param._type._name
                
                return self.__genStructInputFormat(struct)
            elif typeDef._type == TYPE_POINTER:
                struct = STRUCT_DICT.get(typeDef._name)
                if struct == None:
                    return 'i'
                
                return self.__genStructInputFormat(struct)
            elif typeDef._type == TYPE_REF:
                struct = STRUCT_DICT.get(typeDef._name)
                if struct == None:
                    format = self.__getFormatAtom(typeDef._name)
                    if format == None:
                        if typeDef._name[-1] == '*':
                            return 'i'

                        raise 'ERROR: unknown ref type %s' % typeDef._name
                    else:
                        return format
                    
                return self.__genStructInputFormat(struct)
        elif usage == USAGE_INOUT:
            if typeDef._type == TYPE_NORMAL:
                raise 'ERROR: inout param should be pointer or reference.'
            elif typeDef._type in [TYPE_POINTER, TYPE_REF]:
                struct = STRUCT_DICT.get(typeDef._name)
                if struct == None:
                    format = self.__getFormatAtom(typeDef._name)
                    if format == None:
                        if typeDef._name[-1] == '*':
                            return 'i'
                        raise 'ERROR: unknown type %s' % typeDef._name
                    return format
                
                return self.__genStructInputFormat(struct)
                
        return ''
               
    def __genInputDef(self, param):
        if param._usage == USAGE_IN:
            if param._type._type in [TYPE_NORMAL, TYPE_REF]:
                return [(param._type._name, param._name)]
            elif param._type._type == TYPE_POINTER:
                struct = STRUCT_DICT.get(param._type._name, None)
                if struct == None:
                    return [('%s*' % param._type._name, param._name)]
                else:
                    return [(param._type._name, param._name)]
        elif param._usage == USAGE_INOUT:
            if param._type._type in [TYPE_POINTER, TYPE_REF]:
                return [(param._type._name, param._name)]
            else:
                raise 'ERROR: inout param should be pointer or reference.'
                
        return []
        
    def __genOutDefList(self, param):
        if param._usage == USAGE_OUT:
            if param._type._type == TYPE_NORMAL:
                raise 'ERROR: inout param should be pointer or reference.'
            elif param._type._type == TYPE_POINTER:
                struct = STRUCT_DICT.get(param._type._name, None)
                if struct == None:
                    return [('%s*' % param._type._name, parma._name)]
                else:
                    return [(param._type._name, param._name)]
            elif param._type._type == TYPE_REF:
                return [(param._type._name, param._name)]

        return []        
    
    def __genStructInputParamList(self, struct):
        ret = []
        for i in struct._paramList:
            ret += self.__genInputParamList(i, USAGE_IN)
        return ret
    
    def __genInputParamList(self, param, usage):
        if usage in [USAGE_IN, USAGE_INOUT]:
            if param._type._type == TYPE_NORMAL:
                if usage == USAGE_INOUT:
                    raise 'ERROR: inout param should be pointer or reference.'
                
                for t in ATOM_TYPE_LIST:
                    if param._type._name in t:
                        return [param._name]
                struct = STRUCT_DICT.get(param._type._name, None)
                if struct == None:
                    raise 'ERROR: Unknown type %s' % param._type._name
                
                ret = self.__genStructInputParamList(struct)
                return ['%s.%s' % (param._name, i) for i in ret]
            elif param._type._type in [TYPE_POINTER, TYPE_REF]:
                struct = STRUCT_DICT.get(param._type._name, None)
                if struct == None:
                    return [param._name]
                
                ret = self.__genStructInputParamList(struct)
                return ['%s.%s' % (param._name, i) for i in ret]
        return []
        
    def __genCallParamList(self, p):
        if p._usage == USAGE_IN:
            if p._type._type == TYPE_POINTER:
                struct = STRUCT_DICT.get(p._type._name, None)
                if struct == None:
                    return [p._name]
                else:
                    return ['&%s' % p._name]
                    
            return [p._name]
        elif p._usage in [USAGE_INOUT, USAGE_OUT]:
            if p._type._type == TYPE_POINTER:
                return ['&%s' % p._name]
            
            return [p._name]

        return []
        
    def __genStructReturnFormat(self, struct):
        fmt = ''
        for i in struct._paramList:
            fmt += self.__genReturnFormat(i._type)
        return fmt

    def __genReturnFormat(self, typeDef):
        if typeDef._type == TYPE_REF:
            struct = STRUCT_DICT.get(typeDef._name, None)
            if struct == None:
                format = self.__getFormatAtom(typeDef._name)
                if format == None:
                    if typeDef._name[-1] == '*':
                        return 'i'
                    raise 'ERROR: unknown type %s' % typeDef._name
                return format
            
            return self.__genStructReturnFormat(struct)
        elif typeDef._type == TYPE_POINTER:
            struct = STRUCT_DICT.get(typeDef._name, None)
            if struct == None:
                return 'i'
            
            return self.__genStructReturnFormat(struct)
        else:
            return self.__getFormatAtom(typeDef._name)
        
    def __genStructReturnParamList(self, struct):
        ret = []
        for i in struct._paramList:
            ret += self.__genReturnParamList(i._type, i._name)
        return ret
        
    def __genReturnParamList(self, typeDef, name):
        struct = STRUCT_DICT.get(typeDef._name, None)
        if struct == None:
            if typeDef._name in STD_STR_TYPE:
                return ['%s.c_str()' % name]
            else:
                return [name]
        
        ret = self.__genStructReturnParamList(struct)
        ret = ['%s.%s' % (name, i) for i in ret]
        return ret
                
    def __genReturnValueParamList(self, typeDef, name):
        struct = STRUCT_DICT.get(typeDef._name, None)
        if struct == None:
            return [name]
        
        ret = self.__genStructReturnParamList(struct)
        if typeDef._type == TYPE_POINTER:
            ret = ['%s->%s' % (name, i) for i in ret]
        else:
            ret = ['%s.%s' % (name, i) for i in ret]
        return ret
        
    def __genFuncImpl(self, func):
        for p in func[0]._paramList:
            if p._usage in [USAGE_IN, USAGE_INOUT]:
                func[1]._inputDefList += self.__genInputDef(p)
                func[1]._inputFmt += self.__genInputFormat(p._type, p._usage)       
                func[1]._inputParamList += self.__genInputParamList(p, p._usage)

        for p in func[0]._paramList:
            if p._usage == USAGE_OUT:
                func[1]._outDefList += self.__genOutDefList(p)
  
        for p in func[0]._paramList:
            func[1]._callParamList += self.__genCallParamList(p)
        
        if not func[0]._retType._type in [TYPE_CTOR, TYPE_DTOR, TYPE_CAST]:
            ret = self.__genReturnFormat(func[0]._retType)
            if ret:
                func[1]._returnFmt += ret
            else:
                if not func[0]._retType._name in VOID_TYPE:
                    struct = STRUCT_DICT.get(func[0]._retType._name, None)
                    if struct == None:
                        raise 'ERROR: unknown type %s' % func[0]._retType._name
                    func[1]._returnFmt += self.__genStructReturnFormat(struct)
                    
        if not func[0]._retType._name in VOID_TYPE:
            if func[0]._retType._name in STD_STR_TYPE:
                val_name = '__ret__.c_str()'
            else:
                val_name = '__ret__'
            ret = self.__genReturnValueParamList(func[0]._retType, val_name)
            if ret:
                func[1]._returnParamList += ret

        for p in func[0]._paramList:
            if p._usage in [USAGE_INOUT, USAGE_OUT]:
                func[1]._returnFmt += self.__genReturnFormat(p._type)
                param = self.__genReturnParamList(p._type, p._name)
                func[1]._returnParamList += param
                
        if self._config['type'] == 'instance':
            if func[0]._retType._type != TYPE_CTOR:
                func[1]._inputDefList += [('%s*' % self._config['class'], '__obj__')]
                func[1]._inputFmt = 'i' + func[1]._inputFmt
                func[1]._inputParamList = ['__obj__'] + func[1]._inputParamList
            
                                     
    def __generateImpl(self):
        for func in self._funcList:
            self.__genFuncImpl(func)
    
    def __writeResult(self):
        for func in self._funcList:
            txt = '// %s\n' % func[2]
            
            if func[0]._retType._type == TYPE_CTOR:
                txt += 'static PyObject* __NEW__(PyObject* self, PyObject* param)\n{\n'
            elif func[0]._retType._type == TYPE_DTOR:
                txt += 'static PyObject* __DELETE__(PyObject* self, PyObject* param)\n{\n'
            elif func[0]._retType._type == TYPE_CAST:
                txt += 'static PyObject* %s(PyObject* self, PyObject* param)\n{\n' % func[0]._name
            else:                
                txt += 'static PyObject* %s%s(PyObject* self, PyObject* param)\n{\n' % (
                                    self._config['method prefix'], func[0]._name)
            
            for i in func[1]._inputDefList:
                txt += '\t%s %s;\n' % (i[0], i[1])
                
            paramList = ''
            for i in func[1]._inputParamList:
                paramList += ', &%s' % (i)
                
            if func[1]._inputFmt:
                txt += '\tif (!PyArg_ParseTuple(param, "%s"%s))\n\t\tPy_RETURN_NONE;\n\n' % (func[1]._inputFmt, paramList)
            
            for i in func[1]._outDefList:
                txt += '\t%s %s;\n' % (i[0], i[1])
                
            txt += '\t'
            if not func[0]._retType._name in VOID_TYPE:
                if func[0]._retType._type == TYPE_POINTER:
                    txt += '%s* __ret__ = ' % func[0]._retType._name
                elif func[0]._retType._type == TYPE_REF:
                    txt += '%s& __ret__ = ' % func[0]._retType._name
                elif func[0]._retType._type == TYPE_CTOR:
                    txt += '%s* __ret__ = ' % self._config['class']
                elif func[0]._retType._type == TYPE_DTOR:
                    pass                  
                elif func[0]._retType._type == TYPE_CAST:
                    txt += '%s* __ret__ = ' % func[0]._retType._name
                else:
                    txt += '%s __ret__ = ' % func[0]._retType._name
                            
            if func[0]._retType._type == TYPE_DTOR:
                txt += 'delete (%s*)__obj__;\n' % self._config['class']
                txt += '\tPy_RETURN_NONE;\n'
                txt += '}\n\n'
            elif func[0]._retType._type == TYPE_CAST:
                txt += '(%s*)__obj__;\n' % func[0]._retType._name
                txt += '\treturn Py_BuildValue("i", __ret__);\n'
                txt += '}\n\n'                
            else:      
                if func[0]._retType._type == TYPE_CTOR:
                    txt += 'new %s(' % self._config['class']
                    for i in func[1]._callParamList:
                        if func[1]._callParamList.index(i) == 0:
                            txt += i
                        else:
                            txt += ', %s' % i
                    txt += ');\n'                    
                    txt += '\treturn Py_BuildValue("i", __ret__);\n'
                else:
                    if self._config['type'] == 'instance':
                        txt += '__obj__->%s(' % func[0]._name
                    elif self._config['type'] == 'singleton':
                        txt += '%s%s(' % (self._config['singleton'], func[0]._name)
                    elif self._config['type'] == 'function':
                        txt += '%s(' % func[0]._name
                    elif self._config['type'] == 'class_static':
                        txt += '%s::%s(' % (self._config['class'], func[0]._name)                        
                        
                    for i in func[1]._callParamList:
                        if func[1]._callParamList.index(i) == 0:
                            txt += i
                        else:
                            txt += ', %s' % i
                    txt += ');\n'
                    
                    if func[1]._returnFmt:
                        txt += '\treturn Py_BuildValue("%s"' % func[1]._returnFmt
                        for i in func[1]._returnParamList:
                            txt += ', %s' % i
                        txt += ');\n'
                    else:
                        txt += '\tPy_RETURN_NONE;\n'
                
                txt += '}\n\n'
            
            self._pyfilecpp.write(txt)
    
    def __tryExpandMacroDEF_SET_GET(self, tokenList):
        if len(tokenList) == 6 and tokenList[0] == 'DEF_SET_GET':
            T, C = tokenList[2], tokenList[4]
            tl_set = ['void', 'Set%s' % C, '(', T, 'v', ')', ';']    
            tl_get = [T, 'Get%s' % C, '(', ')', ';']
            return [tl_set, tl_get]
        
        return []
        
    def __tryExpandMacroMethod(self, tokenList):
        ret = self.__tryExpandMacroDEF_SET_GET(tokenList)
        if ret:
            return ret
            
        return []
        
    def __parseConfig(self):
        register = ''
        if self._config['namespace']:
            register += 'namespace %s\n{\n\t' % self._config['namespace']

        register += 'PyMethodDef %s_API[] = {\n' % self._config['module name']
                
        self._funcList = []
        methodList = self._config['method list'].split(';')
        for method in methodList:
            if not method.strip():
                continue
            
            tokenList = lex.lexMethod(method)
            macroMethod = self.__tryExpandMacroMethod(tokenList)
            funcList = []
            if macroMethod:
                for tl in macroMethod:
                    func = self.__parseMethod(tl)
                    funcList.append(func)        
            else:
                func = self.__parseMethod(tokenList)
                funcList.append(func)
                
            for func in funcList:
                self._funcList.append((func, FUNC_IMPL(), method.strip()))
           
                if self._config['namespace']:
                    register += '\t'
                    
                if func._retType._type == TYPE_CTOR:
                    register += '\t{"_New", __NEW__, METH_VARARGS, NULL},\n'
                elif func._retType._type == TYPE_DTOR:
                    register += '\t{"_Delete", __DELETE__, METH_VARARGS, NULL},\n'
                else:
                    register += '\t{"%s", %s%s, METH_VARARGS, NULL},\n' % (
                                             func._name, 
                                             self._config['method prefix'], 
                                             func._name)    

        if self._config['type'] == 'instance':
            baseClassList = self._config.get('base', [])
            for base in baseClassList:
                func = FUNCTION()
                func._retType = TYPE_DEF(base, TYPE_CAST)
                func._name = '__CAST_%s' % base
                funcList.append(func)
                self._funcList.append((func, FUNC_IMPL(), func._name))
                
                if self._config['namespace']:
                    register += '\t'
                    
                register += '\t{"_%s", __CAST_%s, METH_VARARGS, NULL},\n' % (base, base)
                                                                         
        if self._config['namespace']:
            register += '\t'
            
        register += '\t{NULL, NULL, NULL, NULL}\n'
        
        if self._config['namespace']:
            register += '\t'
            
        register += '};\n'
        
        if self._config['namespace']:
            register += '}\n'

        return register
            
    def __parseMethod(self, tokenList):
        func = FUNCTION()
        posps = self.__getTokenPos(tokenList, '(', 0, len(tokenList))
        if posps == 0 or posps == len(tokenList) - 1:
            raise 'ERROR: bad method declaration!\n%s' % tokenList
            
        if posps == 1 and tokenList[posps - 1] != self._config['class']:
            raise 'ERROR: bad method declaration!\n%s' % tokenList    
        
        if posps == 1:
            func._name = '__NEW__'
            func._retType = TYPE_DEF('', TYPE_CTOR)
        else:
            func._name = tokenList[posps - 1]
            func._retType = self.__parseTypeTokenList(tokenList[0 : posps - 1])
                
        if func._retType._type == TYPE_DTOR:
            func._name = '__DELETE__'
        
        pospe = self.__getTokenPos(tokenList, ')', 0, len(tokenList))
        if pospe <= posps:
            raise 'ERROR: bad method declaration!\n%s' % tokenList
        
        if pospe - posps > 1:
            ps = posps + 1        
            while True:
                pe = self.__getTokenPos(tokenList, ',', ps, pospe)
                if pe < ps:
                    if pospe - ps < 0:                   
                        break
                    else:
                        pe = pospe
                
                param = FUNC_PARAM()
                param._name = tokenList[pe - 1]
                pl = tokenList[ps : pe - 1]
                if pl[0] == '/':
                    if pl[1] == '*' and pl[3] == '*' and pl[4] == '/':
                        if pl[2] == 'in':
                            param._usage = USAGE_IN
                        elif pl[2] == 'out':
                            param._usage = USAGE_OUT
                        elif pl[2] == 'inout':
                            param._usage = USAGE_INOUT
                        else:
                            raise 'ERROR: bad param inout description!\n%s' % pl
                    else:
                        raise 'ERROR: bad param inout description!\n%s' % pl
                    
                    pl = pl[5:]
                    
                param._type = self.__parseTypeTokenList(pl)
                
                func._paramList.append(param)
                ps = pe + 1

        return func
    
    def __getTokenPos(self, tokenList, token, start, end):
        for i in xrange(start, end):
            if tokenList[i] == token:
                return i
        
        return -1
    
    def __parseTypeTokenList(self, tokenList):
        if len(tokenList) == 1 and tokenList[0] == '~':
            return TYPE_DEF('', TYPE_DTOR)
            
        name = ''
        for t in tokenList:
            if t in ['*', '&']:
                name += t
            else:
                name += ' %s' % t
                
        name = name.strip()
        for t in TYPE_LIST:
            if name in t:
                return TYPE_DEF(name, TYPE_NORMAL)
            
        if name[-1] == '*':
            return TYPE_DEF(name[:-1], TYPE_POINTER)
        elif name[-1] == '&':
            return TYPE_DEF(name[:-1], TYPE_REF)
            
        raise 'ERROR: unknown type: %s' % name
        
        
cppParser = CppParser()
  




